import { Director, director, Tween } from 'cc';
import { BaseAction } from './BaseAction';

export class ActionManager {
    public static _instance: ActionManager;

    // 当前界面的动画信息
    private curActionInfo: ViewActionInfo;
    private actionInfoStack: Array<ViewActionInfo>;

    /**
     * 构造函数，私有，请通过ActionManager.instance获取实例
     */
    private constructor() {
        this.actionInfoStack = new Array();
    }

    /**
     * 获取ActionManager实例。单例
     */
    public static get instance(): ActionManager {
        if (!this._instance) {
            this._instance = new ActionManager();
        }

        return this._instance;
    }

    /**
     * 有Action组件初始化时调用
     * @param action 
     */
    public onActionInit(action: BaseAction) {
        this.curActionInfo.addAction(action);
    }

    /**
     * 进入新界面时调用
     * @param viewName 界面名 会被作为存储数据用的key值 需唯一
     */
    enterView(viewName: string) {
        this.curActionInfo = new ViewActionInfo(viewName);
        this.actionInfoStack.push(this.curActionInfo);
    }

    /**
     * 新界面start时调用(cocos内置的start函数)
     */
    startView(callback?: Function) {
        this.curActionInfo.analyzeEnterence();
        this.curActionInfo.setViewShowCallback(callback);
        director.once(Director.EVENT_AFTER_UPDATE, this.afterViewFirstUpdate, this);
    }

    /**
     * 界面第一次update后的回调
     */
    private afterViewFirstUpdate() {
        // 触发view_show事件
        this.curActionInfo.playActionsByEvent(BaseAction.EVENT_VIEW_SHOW, this.onViewShowFinish.bind(this));
    }

    /**
     * 希望退出当前界面时调用 会播放view_hide动作序列 播放完毕后调用callback
     * @param callback view_hide动作序列结束后的回调
     */
    exitView(callback?: Function) {
        this.curActionInfo.stopAllEvent();

        this.curActionInfo.setViewHideCallback(callback);
        this.curActionInfo.playActionsByEvent(BaseAction.EVENT_VIEW_HIDE, this.onViewHideFinish.bind(this));
    }

    /**
     * 播放事件动画
     * @param eventName 事件名
     * @param rightNow 是否立即播放。默认为否，会延迟到update之后才进行播放
     * @param callback 动作序列完成后的回调
     * @param beforeStartAction 每个动作执行前的回调 可以在此时修改动作的属性
     */
    public playEvent(eventName: string, rightNow?: boolean, callback?: Function, beforeStartAction?: Function) {
        if (rightNow) {
            this.curActionInfo.playActionsByEvent(eventName, callback, beforeStartAction);
        } else {
            director.once(Director.EVENT_AFTER_UPDATE, () => {
                if (this.curActionInfo) {
                    this.curActionInfo.playActionsByEvent(eventName, callback, beforeStartAction);
                }
            });
        }
    }

    /**
     * view_show动作序列结束后的回调
     */
    private onViewShowFinish() {
        this.curActionInfo.onViewShow();
    }

    /**
     * view_hide动作序列结束后的回调
     * 用来处理动作系统相关的数据
     */
    private onViewHideFinish() {
        let viewInfo = this.actionInfoStack.pop();
        viewInfo.onViewHide();

        this.curActionInfo = this.actionInfoStack[this.actionInfoStack.length - 1];
    }
}

/**
 * 动画信息类
 * 每个界面都会对应一个ViewActionInfo。保存界面动画的相关信息，比如所有动画组件、回调等。
 */
class ViewActionInfo {
    /**
     * 界面名
     */
    public name: string;
    /**
     * 动作map key:事件名 value:属于事件的所有动作
     */
    private actionMap: Map<string, BaseAction[]>;
    /**
     * 事件状态map key:事件名 value:状态实例
     */
    private eventStateMap: Map<string, EventState>;
    /**
     * 入口动作map key:事件名 value:属于事件的所有入口动作
     */
    private enterenceActionMap: Map<string, BaseAction[]>;

    private viewHideCallback: Function;
    private viewShowCallback: Function;

    constructor(name: string) {
        this.name = name;
        this.actionMap = new Map();
        this.enterenceActionMap = new Map();
        this.eventStateMap = new Map();
    }

    /**
     * 增加一个动作
     * @param action 
     */
    addAction(action: BaseAction) {
        let event = action.event;
        let actions = this.actionMap.get(event);
        if (!actions) {
            this.actionMap.set(event, [action]);
        } else {
            actions.push(action);
        }
    }

    /**
     * 解析入口动作
     */
    analyzeEnterence() {
        this.actionMap.forEach((actions, event) => {
            let enterenceActionSet = new Set(actions);

            for (let action of actions) {
                let nextActions = BaseAction.findNextActions(action);
                for (let nAction of nextActions) {
                    enterenceActionSet.delete(nAction);
                }
                action.nextActions = nextActions;
            }

            this.enterenceActionMap.set(event, Array.from(enterenceActionSet));
        });
    }

    /**
     * 获得事件的入口动作
     * @param event 事件名
     * @returns 入口动作列表
     */
    getEnterenceAction(event: string): BaseAction[] {
        return this.enterenceActionMap.get(event);
    }

    /**
     * 获得事件的所有动作
     * @param event 事件名
     * @returns 动作列表
     */
    getActions(event: string): BaseAction[] {
        return this.actionMap.get(event);
    }

    /**
     * 播放动作事件序列
     * @param event 事件名
     * @param callback 播放完成回调
     */
    playActionsByEvent(event: string, callback?: Function, beforeStartAction?: Function) {
        let enterences = this.getEnterenceAction(event);
        if (!enterences || !enterences.length) {
            callback && callback();
            return;
        }

        let eventActionAmount = this.getActions(event).length;

        let state = new EventState(eventActionAmount, this, event, callback);
        this.eventStateMap.set(event, state);

        for (let action of enterences) {
            if (action.enabled) {
                action.playAction(this.onActionFinish.bind(this), beforeStartAction);
            } else {
                this.onActionFinish(action);
            }
        }
    }

    /**
     * 事件动作序列播放完成时的回调
     * @param event 事件名
     */
    onEventPlayFinish(event: string) {
        let enterences = this.getEnterenceAction(event);

        for (let action of enterences) {
            action.onEventPlayFinish();
        }
    }

    /**
     * 某个动作播放完成时应调用本函数
     * @param action 
     */
    public onActionFinish(action: BaseAction) {
        let event = action.event;
        let state = this.eventStateMap.get(event);

        state.onActionFinish();
    }

    /**
     * 停止当前界面的所有事件
     */
    public stopAllEvent() {
        this.eventStateMap.forEach((eventState, event) => {
            for (let action of this.actionMap.get(event)) {
                Tween.stopAllByTarget(action.node);
            }

            eventState.onAllActionFinish();
        });
    }

    public setViewHideCallback(callback: Function) {
        this.viewHideCallback = callback;
    }
    public setViewShowCallback(callback: Function) {
        this.viewShowCallback = callback;
    }

    public onViewHide() {
        if (this.viewHideCallback) {
            this.viewHideCallback();
            this.viewHideCallback = undefined;
        }
    }

    public onViewShow() {
        if (this.viewShowCallback) {
            this.viewShowCallback();
            this.viewShowCallback = undefined;
        }
    }
}

/**
 * 事件状态类。
 * 每个播放中的事件动作序列都会有一个对应的状态类。
 * 状态类用来记录当前序列的播放进度，并在结束时通知回调。
 */
class EventState {
    // 事件名
    private event: string;
    // 动作总数
    private actionAmount: number;
    // 动作当前完成数
    private finishAmount: number;
    // 事件序列完成的回调
    private finishCallback: Function;
    // 所属的ViewActionInfo
    private viewActionInfo: ViewActionInfo;

    constructor(actionAmount: number, viewActionInfo: ViewActionInfo, event: string, finishCallback?: Function) {
        this.event = event;
        this.actionAmount = actionAmount;
        this.finishCallback = finishCallback;
        this.viewActionInfo = viewActionInfo;

        this.finishAmount = 0;
    }

    /**
     * 有动作播放完成。正常情况下不需要手动调用本函数
     */
    public onActionFinish() {
        this.finishAmount += 1;

        if (this.finishAmount === this.actionAmount) {
            this.onAllActionFinish();
        }
    }

    /**
     * 全部动作播放完成的回调。正常情况下不需要手动调用本函数
     */
    public onAllActionFinish() {
        this.viewActionInfo.onEventPlayFinish(this.event);
        if (this.finishCallback) {
            this.finishCallback();
            this.finishCallback = undefined;
        }
    }
}